#include "waterview.h"

#include "view.h"
#include "log.h"
#include "system.h"
#include "render.h"
#include "texture.h"
#include "timer.h"
#include "log.h"

#include <string.h>
#include <stdio.h>
#include <memory.h>
#include <stdlib.h>
#include <math.h>

#include "content.h"
#include "scrollbar.h"

#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xmlmemory.h>

typedef struct _WATERVIEW
{
	int *m_iWaterField;  // first water array
	int *m_iWaterField2; // second water array
	int *m_pWaterActive;   // we use these two pointers to flip
	int *m_pWaterScratch;  // the active water array back and forth.
	char m_lutDisplacement[512]; // displacement lookup table (to optimize calculations)

	UFO_Texture srcImage;
	UFO_Timer timer;

	IMAGE output;
	UFO_Texture outputTex;

	int *buffer;
	int widthImage, heightImage;
	long oldTick;
}WATERVIEW;


/****************************************************************************

 MakeDisplacementLookupTable: populates our m_cDisplacement map with valid
 values based on a refraction index.  The refraction index of water is 2.0.

 ****************************************************************************/
void MakeDisplacementLookupTable(char *pDisplacement, int iArraySize, 
                                 float fRefractionIndex, 
                                 float fDepth)
{
	int i = 0;
  for ( i=-iArraySize/2; i < (iArraySize/2)-1; i++) {
    float heightdiff = i*fDepth;
    
    // the angle is the arctan of the height difference
    float angle = (float)atan(heightdiff);

    // now, calculate the angle of the refracted beam.
    float beamangle = (float)asin(sin(angle) / fRefractionIndex);

    // finally, calculate the displacement, based on the refracted beam
    // and the height difference.
    pDisplacement[i+(iArraySize/2)] = (int)(tan(beamangle) * heightdiff);
  }
}

void CreateWaterDroplet(int iX, int iY, int iSize, int iSplashStrength,
	int *waterbuf, int iWaterWidth, int iWaterHeight)
{
	
	int x = 0, y = 0;iY = iWaterHeight - iY;
	for ( x=iX-iSize; x <= iX+iSize; x++) {
		for ( y=iY-iSize; y <= iY+iSize; y++) {
			// make sure we're in bounds
			if (x < 0 || x >= iWaterWidth || y < 0 || y >= iWaterHeight) continue;

			// see if the point at (x,y) is within the circle of radius size
			{
				int square_x    = (x-iX)*(x-iX);
				int square_y    = (y-iY)*(y-iY);
				int square_size = iSize*iSize;

				if (square_x+square_y <= square_size) {
					// it's within the size circle!  apply it to the water buffer.
					waterbuf[(y*iWaterWidth)+x] += (int)((float)iSplashStrength)*sqrt((double)square_x+square_y);
				}
			}
		}
	}
}

/****************************************************************************

 ProcessWater: this function processes our water.  It takes two input buffers,
 the water dimensions, and the cooling amount.  It calculates the new water
 values from waterfield1 and puts them into waterfield2.

 ****************************************************************************/
void ProcessWater(int *oldwater, int *newwater, 
                  int iWaterWidth, int iWaterHeight, float fDampValue, float dt)
{
  // loop through all the water values...
  int y = 0, x = 0;
  for (y=0; y < iWaterHeight; y++) {
    for (x=0; x < iWaterWidth; x++) {

      // add up the values of all the neighboring water values...
      int value;
      int xminus1 = x-1; 
      int xminus2 = x-2; 
      int yminus1 = y-1; 
      int yminus2 = y-2; 

	  int xplus1 = x+1; 
	  int xplus2 = x+2; 
	  int yplus1 = y+1; 
	  int yplus2 = y+2;

	  if (xminus1 < 0) xminus1 = 0;
	  if (xminus2 < 0) xminus2 = 0;
	  if (yminus1 < 0) yminus1 = 0;
	  if (yminus2 < 0) yminus2 = 0;

	  if (xplus1 >= iWaterWidth) xplus1 = iWaterWidth-1;
	  if (xplus2 >= iWaterWidth) xplus2 = iWaterWidth-1;
	  if (yplus1 >= iWaterHeight) yplus1 = iWaterHeight-1;
	  if (yplus2 >= iWaterHeight) yplus2 = iWaterHeight-1;

	  //////////////////////////
	  //
	  // Blending methods: uncomment one of these two methods.
	  //
	  //////////////////////////

      // Method 1: Slower but yields slightly better looking water
        if (iWaterHeight <= 256 && iWaterWidth <= 256) {
        
        value  = (float)oldwater[((y)      *iWaterWidth)+xminus1];
        value += (float)oldwater[((y)      *iWaterWidth)+xminus2];
        value += (float)oldwater[((y)      *iWaterWidth)+xplus1];
        value += (float)oldwater[((y)      *iWaterWidth)+xplus2];
        value += (float)oldwater[((yminus1)*iWaterWidth)+x];
        value += (float)oldwater[((yminus2)*iWaterWidth)+x];
        value += (float)oldwater[((yplus1) *iWaterWidth)+x];
        value += (float)oldwater[((yplus2) *iWaterWidth)+x];
        value += (float)oldwater[((yminus1)*iWaterWidth)+xminus1];
        value += (float)oldwater[((yminus1)*iWaterWidth)+xplus1];
        value += (float)oldwater[((yplus1) *iWaterWidth)+xminus1];
        value += (float)oldwater[((yplus1) *iWaterWidth)+xplus1];
      
        // average them
        value /= 6;
      }

      // Method 2: This method is faster but doesn't look as good (IMHO)
      ///*
        else {
        value  = oldwater[((y)      *iWaterWidth)+xminus1];
        value += oldwater[((y)      *iWaterWidth)+xplus1];
        value += oldwater[((yminus1)*iWaterWidth)+x];
        value += oldwater[((yplus1) *iWaterWidth)+x];
      
        // average them (/4) then multiply by two 
        // so they don't die off as quickly.
        value /= 2;
      }//*/

      ////////////////////////
      //
      // regardless of the blending method we choose, we still must
      // do this stuff.
      //
      ////////////////////////
      
      // subtract the previous water value
      value -= newwater[(y*iWaterWidth)+x];

      // dampen it!
      value = (int)((float)value / (1.04f));

      // store it in array
      newwater[(y*iWaterWidth)+x] = value;
    }
  }
  
  /*
  //unremark this section of code to create a eastbound current
  int y = 0;
  for (y=0; y < iWaterHeight; y++) {
    for (int x=iWaterWidth-1; x >= 0; x--) {
      int xminus1 = x ? x-1 : iWaterWidth-1; // wrap around
      newwater[(y*iWaterWidth)+x] = newwater[(y*iWaterWidth)+xminus1];
    }
  }

  for (y=0; y < iWaterHeight; y++) {
    for (int x=iWaterWidth-1; x >= 1; x--) {
      int xminus1 = x ? x-1 : iWaterWidth-1; // wrap around
      oldwater[(y*iWaterWidth)+x] = oldwater[(y*iWaterWidth)+xminus1];
    }
  }
  //*/
}

int PutWaterOntoTexture(int *waterbuf, int iWaterWidth, int iWaterHeight,
	char *lutDisplacement,
	UFO_Texture srcTex, 
	int *pDest,
	int *moving)
{
	// lock texture
	TEXTURE_LOCKED_RECT rect_src;

	int x, y;

	// our texture surface is now locked, and we can use the pitch to traverse it.
	int *pSrc = 0;

	int dest_index=0;
	int src_pitch = 0;
	int texWidth = ufoTextureGetWidth(srcTex);
	int texHeight = ufoTextureGetHeight(srcTex);
		
	int nCount = iWaterHeight*iWaterWidth;

	ufoTextureLock(srcTex, &rect_src, 0);

	pSrc = (int*)rect_src.pBits;
	src_pitch = rect_src.Pitch/4;
	
	// this could be optimized a LOT.  It's this way so you can learn the technique.
	for ( y=0; y < iWaterHeight; y++) {
		for ( x=0; x < iWaterWidth; x++) {

			int xdiff = (x == iWaterWidth-1)  ? 0 : waterbuf[(y*iWaterWidth)+x+1]   - waterbuf[(y*iWaterWidth)+x];
			int ydiff = (y == iWaterHeight-1) ? 0 : waterbuf[((y+1)*iWaterWidth)+x] - waterbuf[(y*iWaterWidth)+x];

			int xdisp = lutDisplacement[(xdiff+256) % 512];
			int ydisp = lutDisplacement[(ydiff+256) % 512];

			if (xdiff < 0) {
				if (ydiff < 0) {
					if (y-ydisp < 0 || y-ydisp >= texHeight || x-xdisp < 0 || x-xdisp >= texWidth)
						pDest[dest_index++] = pSrc[0];    
					else
						pDest[dest_index++] = pSrc[((y-ydisp)*src_pitch)+x-xdisp];  
				}
				else {
					if (y+ydisp < 0 || y+ydisp >= texHeight || x-xdisp < 0 || x-xdisp >= texWidth)
						pDest[dest_index++] = pSrc[0];    
					else
						pDest[dest_index++] = pSrc[((y+ydisp)*src_pitch)+x-xdisp];  
				}
			}
			else {
				if (ydiff < 0) {
					if (y-ydisp < 0 || y-ydisp >= texHeight || x+xdisp < 0 || x+xdisp >= texWidth)
						pDest[dest_index++] = pSrc[0];    
					else
						pDest[dest_index++] = pSrc[((y-ydisp)*src_pitch)+x+xdisp];  
				}
				else {
					if (y+ydisp < 0 || y+ydisp >= texHeight || x+xdisp < 0 || x+xdisp >= texWidth)
						pDest[dest_index++] = pSrc[0];    
					else {
						pDest[dest_index++] = pSrc[((y+ydisp)*src_pitch)+x+xdisp];
						nCount--;
					}
				}
			}
		}
		// next line
		dest_index += (src_pitch*4 - (texWidth*4))/4;
	}

	*moving = !(nCount == 0);
	// unlock texture surface

	return 1;
}

int RandomNumber(int iMin, int iMax)
{
	if (iMin == iMax) return(iMin);
	return((rand() % (abs(iMax-iMin)+1))+iMin);
}

int internal_setup_water(UFO_View view)
{
	WATERVIEW *pWaterView = (WATERVIEW*)ufoViewGetUserData(view);
	UFO_Texture background = ufoViewGetBackground(view);
	
	if (background != pWaterView->srcImage) {
		
		pWaterView->widthImage = ufoTextureGetWidth(background);
		pWaterView->heightImage = ufoTextureGetHeight(background);
		pWaterView->srcImage = background;
		
		if (pWaterView->m_iWaterField) {
			free(pWaterView->m_iWaterField);	
		}
		pWaterView->m_iWaterField = malloc(pWaterView->widthImage*pWaterView->heightImage*sizeof(int));
		memset(pWaterView->m_iWaterField, 0, pWaterView->widthImage*pWaterView->heightImage*sizeof(int));

		if (pWaterView->m_iWaterField2) {
			free(pWaterView->m_iWaterField2);	
		}pWaterView->m_iWaterField2 = malloc(pWaterView->widthImage*pWaterView->heightImage*sizeof(int));
		memset(pWaterView->m_iWaterField2, 0, pWaterView->widthImage*pWaterView->heightImage*sizeof(int));

		if (pWaterView->buffer) {
			free(pWaterView->buffer);
		}pWaterView->buffer = malloc(pWaterView->widthImage*pWaterView->heightImage*4);

		pWaterView->output.width = pWaterView->widthImage;
		pWaterView->output.height = pWaterView->heightImage;
		pWaterView->output.pixelSize = 4;
		if (pWaterView->output.buffer) {
			free(pWaterView->output.buffer);
		}pWaterView->output.buffer = malloc(pWaterView->widthImage*pWaterView->heightImage*4);
		
		if (pWaterView->outputTex) {
			ufoTextureDelete(pWaterView->outputTex);
		}pWaterView->outputTex = ufoTextureCreate(&pWaterView->output, 0);

		pWaterView->m_pWaterActive = pWaterView->m_iWaterField;
		pWaterView->m_pWaterScratch = pWaterView->m_iWaterField2;
	}
	return 1;
}

int default_waterView_proc(UFO_View view, int id, void* wParam, void* lParam)
{
	WATERVIEW *pWaterView = (WATERVIEW*)ufoViewGetUserData(view);
	if (!pWaterView) {
		pWaterView = (WATERVIEW*)malloc(sizeof(WATERVIEW));
		memset(pWaterView, 0, sizeof(WATERVIEW));
		ufoViewSetUserData(view, pWaterView);
	}

	switch (id)
	{
	case MSG_PAINT:
		{
			if (pWaterView->outputTex) {
				UFO_PAINT_STRUCT *paint = (UFO_PAINT_STRUCT *)lParam;
				ufoRect dst = {0};
				ufoViewGetRect(view, &dst);
				
				//ufoTextureDraw(0, pWaterView->outputTex, &dst, paint->stencilTest, paint->alpha, 0.0f);
				
				return 1;//### not draw view's background
			}
		}
		break;

	case MSG_GESTURE_PAN:
		{
			internal_setup_water(view);
			if (!pWaterView->timer) {
				pWaterView->timer = ufoTimerCreate(view, 0, -1, 0, 0);
				pWaterView->oldTick = ufoGetCurTime();
			}

			if (pWaterView->srcImage) {
				ufoRect dst = {0};
				ufoPoint *point = (ufoPoint *)wParam;
				int xPos = point->x; 
				int yPos = point->y;
				ufoViewGetRect(view, &dst);
				xPos *= (float)pWaterView->widthImage/(dst.right-dst.left);
				yPos *= (float)pWaterView->heightImage/(dst.bottom-dst.top);
				CreateWaterDroplet(xPos, yPos,
					RandomNumber(5,15), RandomNumber(20,25), 
					pWaterView->m_pWaterActive, pWaterView->widthImage, pWaterView->heightImage);
			}
		}
		break;

	case MSG_TIMER:
		{
			if (pWaterView->srcImage) {
				
				int moving = 0;
				long curTime = ufoGetCurTime();
				ProcessWater(pWaterView->m_pWaterActive, pWaterView->m_pWaterScratch, pWaterView->widthImage, pWaterView->heightImage, 0, curTime-pWaterView->oldTick);
				pWaterView->oldTick = curTime;
				ufoViewSetNeedPaint(view);

				PutWaterOntoTexture(pWaterView->m_pWaterActive, pWaterView->widthImage, pWaterView->heightImage, pWaterView->m_lutDisplacement,
					pWaterView->srcImage, pWaterView->buffer, &moving);

				//ufoImageReload(&pWaterView->output, pWaterView->widthImage, pWaterView->heightImage, 4, (unsigned char*)pWaterView->buffer);

				{
					int *temp = pWaterView->m_pWaterActive;
					pWaterView->m_pWaterActive = pWaterView->m_pWaterScratch;
					pWaterView->m_pWaterScratch = temp;
				}

				if (!moving) {
					ufoTimerDelete(pWaterView->timer);
					pWaterView->timer = 0;
				}
			}
		}
		break;

	case MSG_CREATE:
		{		
			MakeDisplacementLookupTable(pWaterView->m_lutDisplacement, sizeof(pWaterView->m_lutDisplacement), 1.333f, 0.5f);
            ufoViewSetGesture(view, GESTURE_PAN);
		}
		break;
	}
	return ufoViewDefaultProc(view, id, wParam, lParam);
}